用友NCCloud 权限绕过importhttpscer接口任意文件上传

环境

nc cloud 2105

漏洞利用点

sink点在nccloud.web.pfxx.manualload.action.ImportHttpsCertActiondoAction方法中,获取上传的文件,根据上传所指定的文件名,创建文件并写入,并未做任何后缀的过滤,可指定上传文件名跨目录将文件写入到任意路径下。

image-20240324024842226

webfile.getFileName()是从上传的数据包中获取文件名。

image-20240324024858195

路由分析

hotwebs/nccloud/WEB-INF/web.xml配置中,请求路径为/nccloud/mob/*可以调用到nccloud.framework.mob.action.entry.MobController类。

image-20240323102816436

跟入到MobController方法中,doPost方法中,调用了MobDispatcher.doAction方法。

image-20240323102938647

MobDispatcherdoAction方法中,调用了this.excute(operator)方法。

image-20240323103258848

主要逻辑实现在execte方法中,首先调用operator.getRequest()拿到IMobRequest,这个对象封装一些方法用于操作Request对象;

image-20240323103517264

这里的operator是由MobHttpOperator.getInstance()创建,默认的request属性为null,之所以能拿到request,是因为在请求过程中会经过nccMobileFilter过滤器,在doFilter方法中调用了operator.bind方法绑定了Request对象。

image-20240323110120378

然后会调用request.getAction(),获取到请求URI的后缀。

image-20240323110231231

getAction方法中,首先是拿到了请求的URI,判断URI中是否包含/nccloud/mob/,然后以/nccloud/mob/分割URI,取到/nccloud/mob/的后缀,如/nccloud/mob/pfxx/manualload/importhttpscer,那么拿到的就是/pfxx/manualload/importhttpscer,最后再以/进行分割,最后在拼接回去并返回;这样做的目的可能是规避后续通过path查找出错的可能。

image-20240323110304111

后续会调用Locator.find(IActionResource.class),获取到IActionResource对象。

image-20240323175117049

根据传入的IActionResource.class 创建IActionResource对象并返回

image-20240323175035257

然后会以request.getAction()返回的路径作为参数调用resource.find(name)name也是截取到的uri,通过uri查找对应的ActionDefine对象,resourceActionResource对象,调用的是ActionResource对象的find方法。。

image-20240323175754018

跟入ActionResourcefind方法中,调用了ActionDefine.resolveRequest,将截取到的URI分割成长度为3的字符数组,组合成特定格式,从Cache中查找对应的ActionDefine对象并返回。

image-20240324025645145

然后回到MobDispatcherexcute方法中,ActionDefine对象的父类不为IMobileActionIMobAuthenticateAction的情况下会走到excutor.excute(define.getInstance(), webOperator)

image-20240324025901292

这个方法会根据ActionDefine对象的父类,创建不同的对象,并调用该对象的excute方法,nccloud.web.pfxx.manualload.action.ImportHttpsCertAction实现的接口为ICommonAction,创建的是CommonActionExcutor对象,并调用excute方法。

image-20240324030512710

nccloud.framework.web.action.excutor.CommonActionExcutorexcute方法中,这个方法会调用actiondoAction方法,这里的action为截取的uri,并从Cache中查找到的ActionDefine对象。

image-20240324030627273

权限绕过

web.xml中,访问/mob/*,会经过nccMobileFilter过滤器。

image-20240324031839367

platform/mob/login等登录接口,会直接执行doFilter。其他接口访问需要在Header头中添加accessToken,否则会返回401

image-20240324032000997

在获取到token的情况下,会进入到如下分支,创建MobClientInfo对象,调用getter方法获取一些值,这里获取了mobuserid的值进行判断,mobuserid如果不为null,然后会验证传入的token,不一致报异常并返回,这里的验证也存在一些风险,假设没获取到mobuserid,也会执行后续的doFilter绕过对Token的检查,mobuserid是调用(new MobClientInfo()).getMobUserId()获取的,跟入到getMobUserId方法中。。

image-20240324032722694

MobContext可能是个全局变量,首先会调用该变量中的getMobuserid方法获取值,如果没获取到,才会调用this.getAttributeValue(mobuserid, true)Token中获取。

image-20240403162627897

getAttributeValue方法中,拿到Token,调用JwtTokenUtil.getInstance().getPrivateClaimFromToken(token, attributeName)获取mobuserid的值。

image-20240403163038337
1
2
3
4
5
6
7
8
private Object getAttributeValue(String attributeName, boolean isString) {
String token = MobHttpOperator.getInstance().getToken();
if (StringUtils.isEmpty(token)) {
return null;
} else {
return this.accessToken.containFiled(attributeName) ? JwtTokenUtil.getInstance().getPrivateClaimFromToken(token, attributeName) : null;
}
}

getClaimFromToken方法中,调用this.jwtProperties.getSecret()获取密钥,解析传入的JwtToken,获取JWT的主体部分并返回。

image-20240403163335415

JwtProperties.class中,jwt的密钥被设为硬编码defaultSecret,通过此密钥,可以伪造jwt数据,在JwtToken中不包含mobuserid,默认会返回null,后续判断即可绕过对Token的校验,直接执行doFilter

image-20240403170005126

JwtTokenUtil.class类中,调用generateToken方法可生成一段符合需求的jwt数据。

image-20240403172230633
1
eyJhbGciOiJIUzUxMiJ9.eyJyYW5kb21LZXkiOiIxIiwic3ViIjoiYWRtaW4iLCJleHAiOjU4MDQ3MzU0NzAsImlhdCI6MTcxMjEzNjEyMX0.c6rYljcqHxfXbF0XTwDagJHFSy3ZOmyRBpyIlbCBADTmKEXDEURfzrBSPMybJg_Ho5QTW8cIxXw9F06TT6BvAA
image-20240403172322619